/*
 * InputHDF5.h
 *
 *  Created on: Aug 9, 2013
 *      Author: paste
 */

#ifndef INPUTHDF5_H_
#define INPUTHDF5_H_

#include "BaseHDF5.h"
#include <boost/optional.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/is_complex.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/is_float.hpp>
#include <boost/multi_array.hpp>

// Forward declarations
namespace H5 {
class DataSet;
}

class InputHDF5 : public virtual BaseHDF5
{
public:

  InputHDF5(const std::string &filename) :
      BaseHDF5(filename, APPEND)
  {
  }

  virtual ~InputHDF5() throw ()
  {
  }

  template<typename StoredType>
  StoredType Get(const std::string &path) const
  {
    return Get<StoredType>(path.c_str());
  }

  template<typename StoredType>
  StoredType Get(const char *path) const
  {
    boost::optional<StoredType> out = GetOptional<StoredType>(path);
    if (out) {
      return out.get();
    } else {
      // TODO Throw something reasonable here
    }
  }

  template<typename StoredType>
  StoredType Get(const std::vector<std::string> &path) const
  {
    const std::string pathStr = boost::algorithm::join(path, "/");
    return Get<StoredType>(pathStr.c_str());
  }

  template<typename StoredType>
  boost::optional<StoredType> GetOptional(const std::string &path) const
  {
    return GetOptional<StoredType>(path.c_str());
  }

  template<typename StoredType>
  boost::optional<StoredType> GetOptional(const char *path) const
  {
    H5::DataSet ds = Traverse(path);
    return Get<StoredType>(ds);
  }

  template<typename StoredType>
  boost::optional<StoredType> GetOptional(
      const std::vector<std::string> &path) const
  {
    const std::string pathStr = boost::algorithm::join(path, "/");
    return GetOptional<StoredType>(pathStr.c_str());
  }

  template<typename StoredType>
  StoredType GetOptional(const std::string &path, const StoredType &def) const
  {
    return GetOptional<StoredType>(path.c_str(), def);
  }

  template<typename StoredType>
  StoredType GetOptional(const char *path, const StoredType &def) const
  {
    boost::optional<StoredType> out = GetOptional<StoredType>(path);
    if (!out) {
      return boost::optional<StoredType>(def);
    }
    return out;
  }

  template<typename StoredType>
  StoredType GetOptional(const std::vector<std::string> &path,
      const StoredType &def) const
  {
    const std::string pathStr = boost::algorithm::join(path, "/");
    return GetOptional<StoredType>(pathStr.c_str(), def);
  }

protected:

  template<typename StoredType>
  boost::optional<StoredType> Get(const H5::DataSet &dataSet) const;

  /**
   * Template specializations for integral types
   */
  template<>
  boost::optional<char> Get<char>(const H5::DataSet &dataSet) const;

  template<>
  boost::optional<unsigned char> Get<unsigned char>(
      const H5::DataSet &dataSet) const;

  template<>
  boost::optional<short> Get<short>(const H5::DataSet &dataSet) const;

  template<>
  boost::optional<unsigned short> Get<unsigned short>(
      const H5::DataSet &dataSet) const;

  template<>
  boost::optional<int> Get<int>(const H5::DataSet &dataSet) const;

  template<>
  boost::optional<unsigned int> Get<unsigned int>(
      const H5::DataSet &dataSet) const;

  template<>
  boost::optional<long> Get<long>(const H5::DataSet &dataSet) const;

  template<>
  boost::optional<unsigned long> Get<unsigned long>(
      const H5::DataSet &dataSet) const;

  /**
   * Template specializations for floating point types
   */
  template<>
  boost::optional<float> Get<float>(const H5::DataSet &dataSet) const;

  template<>
  boost::optional<double> Get<double>(const H5::DataSet &dataSet) const;

  template<>
  boost::optional<long double> Get<long double>(
      const H5::DataSet &dataSet) const;

  /**
   * Template specialization for string
   */
  template<>
  boost::optional<std::string> Get<std::string>(
      const H5::DataSet &dataSet) const;

  /**
   * Template specialization for complex double
   */
  template<>
  boost::optional<std::complex<double> > Get<std::complex<double> >(
      const H5::DataSet &dataSet) const;

  /**
   * Template specialization for vector of doubles
   */
  template<>
  boost::optional<std::vector<double> > Get<std::vector<double> >(
      const H5::DataSet &dataSet) const;

  /**
   * Template specialization for vector of complex doubles
   */
  template<>
  boost::optional<std::vector<std::complex<double> > > Get<
      std::vector<std::complex<double> > >(const H5::DataSet &dataSet) const;

private:

  H5::DataSet Traverse(const char *path) const;

  /**
   * Template specialization for floating point types
   */
  template<typename StoredType>
  typename boost::enable_if<boost::is_float<StoredType>,
      boost::optional<StoredType> >::type Get(const H5::DataSet &dataSet) const;

  /**
   * Template specialization for complex floating point types
   */
  template<typename StoredType>
  typename boost::enable_if<boost::is_complex<StoredType>,
      boost::optional<StoredType> >::type Get(const H5::DataSet &dataSet) const;

  /**
   * Template specialization for a vector of doubles
   */
  template<>
  boost::optional<std::vector<double> > Get<std::vector<double> >(
      const H5::DataSet &dataSet) const;

  /**
   * Template specialization for a multi_array of doubles
   */
  template<int NDim>
  boost::optional<boost::multi_array<double, NDim> > Get<
      boost::multi_array<double, NDim> >(const H5::DataSet &dataSet) const;

};
/*
template<typename StoredType>
typename boost::enable_if<boost::is_integral<StoredType>,
    boost::optional<StoredType> >::type InputHDF5::Get(
    const H5::DataSet& dataSet) const
{
  H5::DataType dt = dataSet.getDataType();
  H5T_class_t cls = dt.getClass();
  if (cls != H5T_INTEGER) {
    // TODO throw something reasonable
  }

  // Get the dataspace
  H5::DataSpace dataSpace = dataSet.getSpace();

  // Get the rank of the dataspace
  if (dataSpace.getSimpleExtentNdims() != 0) {
    // TODO throw something reasonable
  }

  // Get the data
  StoredType out;
  dataSet.read(static_cast<void *>(&out), dt);

  return boost::optional<StoredType>(out);
}

template<typename StoredType>
typename boost::enable_if<boost::is_float<StoredType>,
    boost::optional<StoredType> >::type InputHDF5::Get(
    const H5::DataSet& dataSet) const
{
  H5::DataType dt = dataSet.getDataType();
  H5T_class_t cls = dt.getClass();
  if (cls != H5T_FLOAT) {
    // TODO throw something reasonable
  }

  // Get the dataspace
  H5::DataSpace dataSpace = dataSet.getSpace();

  // Get the rank of the dataspace
  if (dataSpace.getSimpleExtentNdims() != 0) {
    // TODO throw something reasonable
  }

  // Get the data
  StoredType out;
  dataSet.read(static_cast<void *>(&out), dt);

  return boost::optional<StoredType>(out);
}
*/
#endif /* INPUTHDF5_H_ */
